/**
    \file  main.cc
    \brief libgist driver program.

    This file defines the main program for libgist's test-driver. The
    program expects to be passed three command line parameters, viz.,
    dataset name, action and algorithm name.

    The dataset name is a mandatory argument and its meaning is decided
    entirely by end-users, who may use it in whatever way they see fit by
    encoding appropriate policies in the configuration system.

    The second parameter, viz., the action, will usually be either
    "train" or "classify" (default). A help action is also available.

    The algorithm name has to be one of the algorithms implemented by
    libgist (see the libgist codebase's src directory; the algorithm name
    will match the source file name; for example, an algorithm
    implemented in src/foo.cc will be named "foo").

    This program expects the path to its input data and other parameters
    to be specified in a config file. By default, it will use
    ~/.gist/config.py as the config file. However, the --config command
    line option can be used to load a different config file. Note that
    the config file is a Python script and users can perform all sorts
    of arbitrary data preprocessing using it.

    Note that the first command line argument to this program, viz., the
    dataset name, is simply passed as-is (without any further
    interpretation) to the configuration script. End-users can decide
    exactly what is to be done with this parameter.
*/

/*
   This file is part of libgist.

   libgist is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2 of the License, or (at your
   option) any later version.

   libgist is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
   for more details.

   You should have received a copy of the GNU General Public License
   along with libgist; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/

/*
   REVISION HISTORY

   $HeadURL: http://libgist.googlecode.com/svn/branches/wu_dev/drv/main.cc $
   $Id: main.cc 71 2011-09-29 06:22:25Z libgist@gmail.com $
*/

//------------------------------ HEADERS --------------------------------

// gistdrv
#include "action.hh"
#include "algorithm_tester.hh"
#include "config.hh"
#include "exceptions.hh"
#include "factory.hh"
#include "math_utils.hh"
#include "gistdrv_version.hh"

// libgist
#include "settings.hh"
#include "version.hh"

// Boost
#include <boost/program_options.hpp>

// Standard C++
#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>
#include <memory>

// Standard C
#include <stdlib.h>

//----------------------- PROGRAM RETURN CODES --------------------------

namespace {

enum error_codes {
   BAD_DATASET = 1,
   BAD_ALGORITHM,
   BAD_ACTION,
} ;

} // end of local anonymous namespace encapsulating above constants

//---------------------------- BASIC HELP -------------------------------

namespace {

std::string purpose()
{
   return "gist -- test-drive libgist's various algorithms" ;
}

std::string copyright()
{
   return "Copyright (C) 2011 Manu Viswanathan" ;
}

std::string usage()
{
   const char* usage =
"usage: gist [-c|--config config_file_name.py] dataset [action] [algorithm]\n"
"       gist [-h|--help]\n"
"       gist [-v|--version]\n"
"       gist [-w|--warranty]\n" ;

   return usage ;
}

void warranty()
{
   std::cout << purpose()   << '\n' ;
   std::cout << copyright() << "\n\n" ;

   const char* gpl_notice =
"libgist is free software; you can redistribute it and/or modify it under the\n"
"terms of the GNU General Public License as published by the Free Software\n"
"Foundation; either version 2 of the License, or (at your option) any\n"
"later version.\n\n"

"libgist is distributed in the hope that it will be useful, but WITHOUT ANY\n"
"WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n"
"FOR A PARTICULAR PURPOSE. See the GNU General Public License for more\n"
"details.\n\n"

"You should have received a copy of the GNU General Public License along\n"
"with libgist; if not, write to the Free Software Foundation, Inc., 59 Temple\n"
"Place, Suite 330, Boston, MA 02111-1307 USA.\n" ;
   std::cout << gpl_notice ;
}

void help()
{
   std::cout << purpose()  << '\n' ;
   std::cout << "version " << gistdrv::version() << ", "
             << copyright()<< "\n\n" ;
   std::cout << usage()    << '\n' ;

   const char* help =
"This program test-drives the different gist algorithms implemented by\n"
"libgist. It should be invoked thusly:\n"
"\tgist dataset action algorithm\n\n"

"The first command line argument is mandatory and it specifies the name of\n"
"a dataset. The libgist driver program does not itself have any specific\n"
"notions or requirements of a dataset except that it comprises of several\n"
"images used for training and classification. Exactly how a dataset is\n"
"stored and organized is entirely up to end-users. Dataset names are also\n"
"user-specified; they could be aliases or full path names or whatever.\n\n"

"Given that this driver program transfers all representational requirements\n"
"about datasets to its users, all it does with the first command line\n"
"argument is to pass it to the libgist configuration system, which is\n"
"implemented as a (collection of) Python script(s). That is, before launching\n"
"a gist algorithm, passing it data, etc., this driver will first execute\n"
"a Python script that it expects will setup various parameters such as the\n"
"list of scene categories and their corresponding training/testing images.\n\n"

"The advantage of using a full-blown programming language over a simple\n"
"configuration file in order to customize different aspects of this program\n"
"is that end-users can perform all sorts of arbitrary data preprocessing\n"
"steps that are specific to their particular situations prior to executing\n"
"different scene categorization tasks. A simple, non-programmable config\n"
"file would have encoded many policies within this driver program, which\n"
"would have been inflexible and, likely, nonsensical in a context other than\n"
"the one in which the original libgist developers/authors worked.\n\n"

"The disadvantage of using a programming language for configuration is that\n"
"it places a greater burden on end-users, especially those unfamiliar with\n"
"programming and, in this case, Python. To ease this burden, the libgist\n"
"codebase ships with some, hopefully portable and general, Python config\n"
"scripts. You will still need to edit a few settings, but that should be\n"
"a reasonably straightforward thing to do.\n\n"

"By default, this program will load its settings by executing the Python\n"
"code stored in ~/.gist/config.py. Use the --config-file option to specify\n"
"a different configuration script.\n\n"

"The second command line argument specifies the action to perform. It is\n"
"optional and defaults to \"classify\". Thus, if you do not specify an\n"
"action, the gist program will classify images in the dataset named by the\n"
"first argument. Exactly which images get classified depends on the policy\n"
"implemented by the Python customization/configuration script described\n"
"above.\n\n"

"Apart from the default classify action, the gist program supports one\n"
"other action, viz., train. Most scene categorization algorithms need to\n"
"be trained before they can successfully classify new images. Thus,\n"
"although \"classify\" is the default action, you will need to first run\n"
"the libgist algorithms in training mode. Again, exactly what images you\n"
"use to train the algorithms depends entirely on you and that policy should\n"
"be encoded in the Python config script.\n\n"

"The third command line argument specifies the name of the gist algorithm\n"
"to use. It is optional and defaults to \"all\". Thus, if you skip this\n"
"argument, the libgist driver will perform the selected action on all the\n"
"algorithms available in libgist.\n\n"

"The following command line options are supported:\n\n"

"-c|--config config_file.py\n"
"\tSpecify a Python config script other than the default\n"
"\t(which is ~/.gist/config.py).\n\n"

"-h|--help\n"
"\tPrint this (hopefully) useful info.\n\n"

"-v|--version\n"
"\tPrint the current version number of this program.\n\n"

"-w|--warranty\n"
"\tPrint warranty (or rather, lack thereof).\n\n" ;
   std::cout << help ;
}

} // end of local anonymous namespace encapsulating above helpers

//----------------------- COMMAND LINE PARSING --------------------------

namespace {

// Quick helper to package command line args together
struct cmdline {
   std::string dataset;
   std::string action ;
   std::string algo   ;
   std::string config ;
} ;

// If the user does not supply the -c option on the command line, we will
// fall back to the default config file name returned by this function.
std::string default_config_file()
{
   return std::string(getenv("HOME")) + "/.gist/config.py" ;
}

// Helper function to take care of the annoying details of using
// Boost.program_options to get at the command line arguments.
//
// DEVNOTE: This code is lifted almost verbatim from the
// Boost.program_options tutorial and examples that comes with the Boost
// documentation. There may be better (i.e., neater, more effective,
// clearer, more efficient, whatever) ways to use the library.
cmdline parse(int argc, char* argv[])
{
   cmdline params ;

   // Specify the command line options
   namespace po = boost::program_options ;
   po::options_description options("Command line options") ;
   options.add_options()
      // -c: specify config file (default = ~/.gist/config.py)
      ("config,c",
       po::value<std::string>(&params.config)->
          default_value(default_config_file()),
       "specify configuration settings file")

      // --dataset: specify gist dataset to use
      ("dataset",
       po::value<std::string>(&params.dataset),
       "name of dataset to be used for training/classification")

      // --action: specify gist training/testing action (default = classify)
      ("action",
       po::value<std::string>(&params.action)->default_value("classify"),
       "the gist training/classification action to perform")

      // --algo: specify gist algorithm (default = all)
      ("algo",
       po::value<std::string>(&params.algo)->default_value("all"),
       "gist algorithm to use")

      // -v: print version info
      ("version,v", "print program version")

      // -h: print help
      ("help,h", "print some help")

      // -w: print warranty
      ("warranty,w", "print warranty") ;

   // Convert first positional command line argument to --dataset, the
   // second one to --action and the third to --algo.
   po::positional_options_description p ;
   p.add("dataset",1) ;
   p.add("action", 1) ;
   p.add("algo",   1) ;

   // Setup done: now parse argc and argv...
   po::variables_map varmap ;
   po::store(po::command_line_parser(argc, argv).
             options(options).positional(p).run(), varmap) ;
   po::notify(varmap) ;

   // Process -h, -v and -w
   if (varmap.count("help")) {
      help() ;
      exit(0) ;
   }
   if (varmap.count("version")) {
      std::cout << "libgist: " << gist::version()    << '\n' ;
      std::cout << "gistdrv: " << gistdrv::version() << '\n' ;
      exit(0) ;
   }
   if (varmap.count("warranty")) {
      warranty() ;
      exit(0) ;
   }

   // User actually wants to exercise one or all of the gist
   // algorithms. But first, let's make sure s/he's supplied a dataset to
   // work with...
   if (params.dataset.empty()) {
      std::cerr << "gist: please specify dataset name\n" ;
      std::cerr << usage() ;
      exit(BAD_DATASET) ;
   }
   return params ;
}

// Helper function to "convert" the strings stored in the cmdline
// structure into an STL vector that can then be passed as command line
// arguments to the Python program that loads the libgist config
// settings.
std::vector<std::string>
config_params(const cmdline& params)
{
   std::vector<std::string> p ;
   p.reserve(3) ;
   p.push_back(params.dataset);
   p.push_back(params.action) ;
   p.push_back(params.algo)   ;
   return p ;
}

} // end of local anonymous namespace encapsulating above helpers

//---------------------- LIBGIST GLOBAL SETTINGS ------------------------

// Quick helper to setup libgist before driver program actually uses it
namespace {

void apply_libgist_global_settings()
{
   using gistdrv::round ;
   using gistdrv::config;

   gist::num_threads(round(config::get<float>("num_threads"))) ;
}

} // end of local anonymous namespace encapsulating above helper

//------------------------------- MAIN ----------------------------------

int main(int argc, char* argv[])
{
   typedef gistdrv::algorithm_tester algo ;
   typedef gistdrv::factory<algo> alg_factory ;
   typedef gistdrv::action actn ;
   typedef gistdrv::factory<actn, algo*> act_factory ;
   try
   {
      cmdline params = parse(argc, argv) ;
      gistdrv::config::load(params.config, config_params(params));
      std::auto_ptr<algo> alg(alg_factory::create(params.algo))  ;
      std::auto_ptr<actn> act(act_factory::create(params.action, alg.get())) ;
      apply_libgist_global_settings() ;
      act->exec() ;
   }
   catch (alg_factory::unknown_type& e)
   {
      std::cerr << "gist: " << e.what()
                << ": unknown algorithm; try --help option for more info\n" ;
      return BAD_ALGORITHM ;
   }
   catch (act_factory::unknown_type& e)
   {
      std::cerr << "gist: " << e.what()
                << ": unknown action; try --help option for more info\n" ;
      return BAD_ACTION ;
   }
   catch (gistdrv::exception& e)
   {
      std::cerr << "gist: " << e.what() << '\n' ;
      return e.code() ;
   }
   catch (std::exception& e)
   {
      std::cerr << "gist: " << e.what() << '\n' ;
      return 255 ;
   }
   return 0 ;
}

//-----------------------------------------------------------------------

/* So things look consistent in everyone's emacs... */
/* Local Variables: */
/* indent-tabs-mode: nil */
/* End: */
